/*
 * Mitchelle Rasquinha
 * wrapper for dramsim2
 */


#include <sys/mman.h>
#include <sys/types.h>
#include <sys/user.h>
#include <fcntl.h>
#include <unistd.h>
#include <zlib.h>

#include <cerrno>
#include <cstdio>
#include <iostream>
#include <string>

#include "arch/isa_traits.hh"
#include "arch/registers.hh"
#include "base/intmath.hh"
#include "base/misc.hh"
#include "base/random.hh"
#include "base/types.hh"
#include "config/full_system.hh"
#include "config/the_isa.hh"
#include "debug/LLSC.hh"
#include "debug/MemoryAccess.hh"
#include "mem/packet_access.hh"
#include "mem/physical.hh"
#include "mem/packet.hh"
#include "sim/eventq.hh"

using namespace std;
using namespace TheISA;

PhysicalMemory::PhysicalMemory(const Params *p)
    : MemObject(p), pmemAddr(NULL), lat(p->latency), lat_var(p->latency_var),
      _size(params()->range.size()), _start(params()->range.start),
      tx_queue_size(p->tx_q),
      dram_clk(1000),
      waitRetry(false), fastfwd(p->fastfwd), ddrConfigStr(p->ddrConfig), megsMem(p->megsMem)
{
    if (size() % TheISA::PageBytes != 0)
        panic("Memory Size not divisible by page size\n");

    warn(" In pmem constructor megsMem%lld", megsMem);

    if (params()->null)
        return;


    if (params()->file == "") {
        int map_flags = MAP_ANON | MAP_PRIVATE;
        pmemAddr = (uint8_t *)mmap(NULL, size(),
                                   PROT_READ | PROT_WRITE, map_flags, -1, 0);
    } else {
        int map_flags = MAP_PRIVATE;
        int fd = open(params()->file.c_str(), O_RDONLY);
        _size = lseek(fd, 0, SEEK_END);
        lseek(fd, 0, SEEK_SET);
        pmemAddr = (uint8_t *)mmap(NULL, roundUp(size(), sysconf(_SC_PAGESIZE)),
                                   PROT_READ | PROT_WRITE, map_flags, fd, 0);
    }

    if (pmemAddr == (void *)MAP_FAILED) {
        perror("mmap");
        if (params()->file == "")
            fatal("Could not mmap!\n");
        else
            fatal("Could not find file: %s\n", params()->file);
    }

    //If requested, initialize all the memory to 0
    if (p->zero)
        memset(pmemAddr, 0, size());

    // Create DRAMSim memory system
    m_memorySystem=new MultiChannelMemorySystem(ddrConfigStr, 
                                    "./dramsim_configs/system.ini", "", 
                                    "results_dramsim.txt",megsMem,NULL,NULL,this);

}

void
PhysicalMemory::init_dramsim()
{

    // Setup dramsim
    warn(" create event for dramsim DRAMSIM clk: %d ps", dram_clk);

    DRAMSim::Callback<PhysicalMemory, void, uint, uint64_t,uint64_t >* read_cb;
    DRAMSim::Callback<PhysicalMemory, void, uint, uint64_t,uint64_t >* write_cb;

    read_cb = new DRAMSim::Callback<PhysicalMemory, void, unsigned, uint64_t, uint64_t>(this, &PhysicalMemory::read_complete);
    write_cb = new DRAMSim::Callback<PhysicalMemory, void, unsigned, uint64_t, uint64_t>(this, &PhysicalMemory::write_complete);

    m_memorySystem->RegisterCallbacks(read_cb, write_cb, NULL);

    // start a clock
    new MemProgressEvent(this, dram_clk);

}

void
PhysicalMemory::init()
{
    if(ports.size() == 0)
    {
        fatal("Dramsim not connected");
    }
    ports[0]->sendStatusChange(Port::RangeChange);

}

PhysicalMemory::~PhysicalMemory()
{
    if (pmemAddr)
        munmap((char*)pmemAddr, size());
}

void
PhysicalMemory::regStats()
{
    using namespace Stats;

    m_memorySystem->regStats();

    bytesRead
        .name(name() + ".bytes_read")
        .desc("Number of bytes read from this memory")
        ;
    bytesInstRead
        .name(name() + ".bytes_inst_read")
        .desc("Number of instructions bytes read from this memory")
        ;
    bytesWritten
        .name(name() + ".bytes_written")
        .desc("Number of bytes written to this memory")
        ;
    numReads
        .name(name() + ".num_reads")
        .desc("Number of read requests responded to by this memory")
        ;
    numWrites
        .name(name() + ".num_writes")
        .desc("Number of write requests responded to by this memory")
        ;
    numOther
        .name(name() + ".num_other")
        .desc("Number of other requests responded to by this memory")
        ;
    bwRead
        .name(name() + ".bw_read")
        .desc("Total read bandwidth from this memory (bytes/s)")
        .precision(0)
        .prereq(bytesRead)
        ;
    bwInstRead
        .name(name() + ".bw_inst_read")
        .desc("Instruction read bandwidth from this memory (bytes/s)")
        .precision(0)
        .prereq(bytesInstRead)
        ;
    bwWrite
        .name(name() + ".bw_write")
        .desc("Write bandwidth from this memory (bytes/s)")
        .precision(0)
        .prereq(bytesWritten)
        ;
    bwTotal
        .name(name() + ".bw_total")
        .desc("Total bandwidth to/from this memory (bytes/s)")
        .precision(0)
        .prereq(bwTotal)
        ;
    rt_lat
        .init(0,250000,50000)
        .name(name() + ".roundTrip_lat")
        .desc("Round trip latency for a dram req")
        .precision(2)
        ;


    bwRead = bytesRead / simSeconds;
    bwInstRead = bytesInstRead / simSeconds;
    bwWrite = bytesWritten / simSeconds;
    bwTotal = (bytesRead + bytesWritten) / simSeconds;
}

unsigned
PhysicalMemory::deviceBlockSize() const
{
    //Can accept anysize request
    return 0;
}

Tick
PhysicalMemory::calculateLatency(void)
{
    //should only be called from doAtomic access and hence setting it to 1ns
    return 1;
}




#if TRACING_ON

#define CASE(A, T)                                                      \
    case sizeof(T):                                                       \
        DPRINTF(MemoryAccess,"%s of size %i on address 0x%x data 0x%x\n",   \
                A, pkt->getSize(), pkt->getAddr(), pkt->get<T>());          \
break


#define TRACE_PACKET(A)                                                 \
    do {                                                                \
        switch (pkt->getSize()) {                                       \
            CASE(A, uint64_t);                                            \
            CASE(A, uint32_t);                                            \
            CASE(A, uint16_t);                                            \
            CASE(A, uint8_t);                                             \
            default:                                                      \
                                                                          DPRINTF(MemoryAccess, "%s of size %i on address 0x%x\n",    \
                                                                                  A, pkt->getSize(), pkt->getAddr());                 \
            DDUMP(MemoryAccess, pkt->getPtr<uint8_t>(), pkt->getSize());\
        }                                                               \
    } while (0)

#else

#define TRACE_PACKET(A)

#endif

void
PhysicalMemory::doAtomicAccess(PacketPtr pkt)
{
    assert(pkt->getAddr() >= start() &&
           pkt->getAddr() + pkt->getSize() <= start() + size());

    if (pkt->memInhibitAsserted()) {
        DPRINTF(MemoryAccess, "mem inhibited on 0x%x: not responding\n",
                pkt->getAddr());
        return;
    }

    uint8_t *hostAddr = pmemAddr + pkt->getAddr() - start();

    if (pkt->isRead()) 
    {
        assert(!pkt->isWrite());
        if (pmemAddr)
            memcpy(pkt->getPtr<uint8_t>(), hostAddr, pkt->getSize());
        TRACE_PACKET(pkt->req->isInstFetch() ? "IFetch" : "Read");
        numReads++;
        bytesRead += pkt->getSize();
        if (pkt->req->isInstFetch())
            bytesInstRead += pkt->getSize();
    } 
    else if (pkt->isWrite()) 
    {
        if (pmemAddr)
            memcpy(hostAddr, pkt->getPtr<uint8_t>(), pkt->getSize());
        assert(!pkt->req->isInstFetch());
        TRACE_PACKET("Write");
        numWrites++;
        bytesWritten += pkt->getSize();
    }
    else if (pkt->isInvalidate()) 
    {
        //upgrade or invalidate
        if (pkt->needsResponse()) 
        {
            pkt->makeAtomicResponse();
        }
    } 
    else 
    {
        panic("unimplemented");
    }

    if (pkt->needsResponse()) {
        pkt->makeAtomicResponse();
    }
    return ; 
}


void
PhysicalMemory::doFunctionalAccess(PacketPtr pkt)
{
    assert(pkt->getAddr() >= start() &&
           pkt->getAddr() + pkt->getSize() <= start() + size());


    uint8_t *hostAddr = pmemAddr + pkt->getAddr() - start();

    if (pkt->isRead()) {
        if (pmemAddr)
            memcpy(pkt->getPtr<uint8_t>(), hostAddr, pkt->getSize());

        TRACE_PACKET("Read");
        pkt->makeAtomicResponse();
    } else if (pkt->isWrite()) {
        if (pmemAddr)
            memcpy(hostAddr, pkt->getPtr<uint8_t>(), pkt->getSize());
        TRACE_PACKET("Write");
        pkt->makeAtomicResponse();
    } else if (pkt->isPrint()) {
        Packet::PrintReqState *prs =
            dynamic_cast<Packet::PrintReqState*>(pkt->senderState);
        // Need to call printLabels() explicitly since we're not going
        // through printObj().
        prs->printLabels();
        // Right now we just print the single byte at the specified address.
        ccprintf(prs->os, "%s%#x\n", prs->curPrefix(), *hostAddr);
    } else {
        panic("PhysicalMemory: unimplemented functional command %s",
              pkt->cmdString());
    }
}

void
PhysicalMemory::getAddressRanges(AddrRangeList &resp, bool &snoop)
{
    snoop = false;
    resp.clear();
    resp.push_back(RangeSize(start(), size()));
}



bool PhysicalMemory::recvTiming(PacketPtr pkt)
{
    // max_outstanding should be equal to max number of requests in memory system 1rank 8 banks 16 entries per bank
    if( max_outstanding > tx_queue_size)
    {
        DPRINTF(MemoryAccess," memport nack @%lld \n",curTick());
        return false;
    }

    pkt->dram_enter_time = curTick();
    if( pkt->isRead() || pkt->isWrite() )
    {
        DPRINTF(MemoryAccess,"recvTiming push recvQ %llx @%lld \n", pkt->getAddr(),curTick());
        recvQ.push_back(pkt);
        max_outstanding++;
    }
    else
    {
        if(pkt->needsResponse())
        {
            DPRINTF(MemoryAccess,"recvTiming needs response push readyQ %llx @%lld \n", pkt->getAddr(), curTick());
            pkt->makeTimingResponse();
            readyQ.push_back(pkt);
        }
        else
            delete pkt;
    }

    return true;
}

void
PhysicalMemory::recvRetry()
{
    DPRINTF(MemoryAccess," Retry: Attempt to send again \n");
    assert(!readyQ.empty() && "no pkts to send but retry was called!!");
    waitRetry = false;
    sendTry();

    return;
}

void
PhysicalMemory::sendTry()
{
    while ( !waitRetry && !readyQ.empty())
    {
        DPRINTF(MemoryAccess," readyQ has entry.. sendTry @%lld \n",curTick());
        PacketPtr pkt = readyQ.front();

        // in a case where we dont use the membus
        // and directly connect the LLC to memory
        // you need to mock the time for some data transfer
        // on a bus.
        pkt->firstWordTime = 2000 + curTick();
        pkt->finishTime = 8000 + curTick();
        if ( !ports[0]->sendTiming(pkt) )
        {
            DPRINTF(MemoryAccess,"send timing failed \n");
            waitRetry = true;
        }
        else
        {
            Tick latency = curTick() - pkt->dram_enter_time;
            rt_lat.sample(latency);
            readyQ.pop_front();
        }
    }
}

Port *
PhysicalMemory::getPort(const std::string &if_name, int idx)
{
    // Accept request for "functional" port for backwards compatibility
    // with places where this function is called from C++.  I'd prefer
    // to move all these into Python someday.
    if (if_name == "functional") {
        return new MemoryPort(csprintf("%s-functional", name()), this);
    }

    if (if_name != "port") 
    {
        panic("PhysicalMemory::getPort: unknown port %s requested", if_name);
    }

    if (idx >= (int)ports.size()) {
        ports.resize(idx + 1);
    }

    if (ports[idx] != NULL) {
        panic("PhysicalMemory::getPort: port %d already assigned", idx);
    }

    MemoryPort *port =
        new MemoryPort(csprintf("%s-port%d", name(), idx), this);

    ports[idx] = port;
    return port;
}


void
PhysicalMemory::recvStatusChange(Port::Status status)
{
    // I think this is where you set address ranges
    return;
}

unsigned int
PhysicalMemory::drain(Event *de)
{
    int count = 0;
    //not a timing port & drain not implemented
    //for (PortIterator pi = ports.begin(); pi != ports.end(); ++pi) 
    //{
    //count += (*pi)->drain(de);
    // }

    if (count)
        changeState(Draining);
    else
        changeState(Drained);
    return count;
}

void
PhysicalMemory::serialize(ostream &os)
{
    return;
}

void
PhysicalMemory::unserialize(Checkpoint *cp, const string &section)
{
    return;
}

PhysicalMemory *
PhysicalMemoryParams::create()
{
    return new PhysicalMemory(this);
}

void
PhysicalMemory::blp_update(uint tID, uint64_t rank, uint64_t bank)
{
    blp[tID][rank][bank]++;
}

void
PhysicalMemory::read_complete(uint id, uint64_t addr, uint64_t clk_cycle )
{
    DPRINTF(MemoryAccess," read_complete for %llx @ %lld \n", addr,curTick());
    assert( !readQ.empty() );

    //readQ.front().second += JEDEC_DATA_BUS_BITS;
    PacketPtr pkt = NULL;
    int pos = -1;
    for ( int i=0; i<readQ.size();i++)
        if ( readQ[i].second == addr )
        {
            pkt = readQ[i].first;
            pos = i;
            break;
        }

    if( pkt)
    {
        doAtomicAccess(pkt);
        readyQ.push_back(pkt);
        readQ.erase(readQ.begin()+pos);
        max_outstanding--;
    }
    else{
        panic(" read_complete dint find the pkt it requested");
    }
}

void
PhysicalMemory::write_complete(uint id, uint64_t addr, uint64_t clk_cycle )
{
    DPRINTF(MemoryAccess," write complete \n" );

    assert( ! writeQ.empty() );

    PacketPtr pkt = NULL;
    int pos = -1;
    for ( int i=0; i<writeQ.size();i++)
        if ( writeQ[i].second == addr )
        {
            pkt = writeQ[i].first;
            pos = i;
            break;
        }


    if( pkt)
    {
        //doAtomicAccess( pkt );
        if ( pkt->needsResponse() ) {
            readyQ.push_back(pkt);
        } else {
            Tick latency = curTick() - pkt->dram_enter_time;
            rt_lat.sample(latency);

            delete pkt;
        }
        max_outstanding--;
        writeQ.erase(writeQ.begin()+pos);
    }
    else{
        panic(" write_complete dint find the pkt it requested");
    }
}

void
PhysicalMemory::clock(void)
{
    m_memorySystem->update();
    sendTry();

    //printf("Current Clock: %ld\n",curTick());
    //Changed this from while to if so that only one request can be serviced 
    //per clock so that correct backpressure is modelled
    if ( ! recvQ.empty() )
    {
        PacketPtr pkt = recvQ.front();
        uint64_t addr = pkt->getAddr(); 

        // check the addr range fits on the data bus
        addr &= ~(JEDEC_DATA_BUS_BITS- 1);

        DPRINTF(MemoryAccess,"@ %lld adding transaction for addr %llx \n", curTick(), addr);

        if(m_memorySystem->willAcceptTransaction(addr)) {
	    if(pkt->isWrite()) {
		assert(pkt->cmd==MemCmd::Writeback);
		doAtomicAccess( pkt );
	    }
            if(pkt->req->hasContextId())
                m_memorySystem->addTransaction( pkt->isWrite(), addr);
            else
                m_memorySystem->addTransaction( pkt->isWrite(), addr);
        }
        else
            return;

        DPRINTF(MemoryAccess," transaction added to dramsim \n");

        if ( pkt )
        {
            if ( pkt->isRead() )
                readQ.push_back( make_pair( pkt, addr));
            else
                writeQ.push_back( make_pair( pkt, addr));

            recvQ.pop_front();
        }
    }

    return;
}

MemProgressEvent::MemProgressEvent(PhysicalMemory *_mem, Tick ival)
:Event(Event::Progress_Event_Pri), _interval(ival),mem(_mem),_repeatEvent(true)
{
    if (_interval)
        mem->schedule(this,curTick()+_interval);
}

void
MemProgressEvent::process()
{
    mem->clock();
    if(_repeatEvent)
        mem->schedule(this,curTick()+_interval);
}

